Lecture 10¶
October 9, 2024
Decorator¶
A decorator is a function that takes as input a function and returns a function. Here is a decorator that takes as input a function $f$ returning a number and returns the function $|f|$. Note that we allow our function to take all types of arguments; the returned function will take the arguments as the original.
def abs_decorator(f):
def new_f(*args, **kwds):
return abs(f(*args, **kwds))
return new_f
For a demonstration of how abs_decorator
works, see the function $x \mapsto \sin \frac{1}{x}$.
def sin_of_recip(x):
return sin(1/x)
Here is the graph of $x \mapsto \sin \frac{1}{x}$.
plot(sin_of_recip(x), (x, -1, 1))
Now we apply abs_decorator
to $x \mapsto \sin \frac{1}{x}$, storing the result in abs_version
.
abs_version = abs_decorator(sin_of_recip)
abs_version
<function abs_decorator.<locals>.new_f at 0x7f429bf96980>
We plot abs_version
. Note that it is the graph of $x \mapsto |\sin \frac{1}{x}|$.
plot(abs_version(x), (x, -1, 1))
Python has a special syntax for applying decorators. The @abs_decorator
below indicates that this decorator should be applied to the function sin_of_recip2
.
@abs_decorator
def sin_of_recip2(x):
return sin(1/x)
Formally what is being done is exactly what we did above. The sin_of_recip2
function is created as defined above, but, after its definition, it is passed through abs_decorator
and the result is stored in sin_of_recip2
. The result is now when we apply sin_of_recip2
we get the function $x \mapsto |\sin \frac1x|$.
plot(sin_of_recip2(x), (x, -1, 1))
Interacts¶
We will learn how to make interactive cells in SageMath. Most functions with default values can be made interactive.
References:
- There is a
SageMath
tutorial on interactive cells. - There are a bunch of examples on SageMath wiki.
The function interact
is a decorator that makes a function interactive. A simple demonstration:
@interact
def myplot(f=x^2):
show(plot(f,(x,-3,3)))
Interactive function <function myplot at 0x7f429bf00540> with 1 widget f: EvalText(value='x^2', description='f')
Above, you should be able to edit the value of f
, changing the function being plotted. Whenever the value of any input changes, the function is recomputed.
Here we have two inputs, both numbers. We construct the line segment from $(0,0)$ to $(x,y)$.
@interact
def my_line(x=2, y=3):
show(line([(0,0), (x,y)]), aspect_ratio=1)
Interactive function <function my_line at 0x7f429bddb100> with 2 widgets x: IntSlider(value=2, description='x', max=6, min=-2) y: IntSlider(value=3, description='y', max=9, min=-3)
We can also do the above example with a vector.
@interact
def my_line(v=vector([3, 2])):
show(line([(0,0), v]), aspect_ratio=1)
Interactive function <function my_line at 0x7f429bcedf80> with 1 widget v: EvalText(value='(3, 2)', description='v')
Here we demonstrate that you can interact with strings.
@interact
def say_hi(name='Benny'):
print(f'Hi {name}!')
Interactive function <function say_hi at 0x7f429bcfc4a0> with 1 widget name: Text(value='Benny', description='name')
Boolean values:
@interact
def say_hi(truth_value = True):
print(f'The statement is {truth_value}!')
Interactive function <function say_hi at 0x7f429bcfc540> with 1 widget truth_value: Checkbox(value=True, description='truth_value')
Use of pairs:¶
If a pair is used, the first item will be the label, and the second will be the default value. For example:
@interact
def say_hi(truth_value = ('Truth value', True)):
print(f'The statement is {truth_value}!')
Interactive function <function say_hi at 0x7f429bced260> with 1 widget truth_value: Checkbox(value=True, description='Truth value')
Option not to update¶
The special auto_update
variable is used to control whether the function should update automatically. Setting it to false gives a button which must be clicked to get an update:
@interact
def my_line(x=2, y=3, auto_update=False):
show(line([(0,0), (x,y)]), aspect_ratio=1)
Manual interactive function <function my_line at 0x7f429bced3a0> with 2 widgets x: IntSlider(value=2, description='x', max=6, min=-2) y: IntSlider(value=3, description='y', max=9, min=-3)
Widgets¶
Input arguments to interacts can be widgets, which give us some more control of how the user can input values.
Input box¶
i = input_box(default=1, label='x')
i
EvalText(value='1', description='x', layout=Layout(max_width='81em'))
To get the value represented by a widget we can use the get_value
method. This is what will be passed to the function using the interact.
i.get_value()
1
By default, Sage evaluates the string before giving access in a function. For example:
@interact
def compute_square(x = input_box(default=1)):
print(f'The square of x is {x^2}')
Interactive function <function compute_square at 0x7f429b98f740> with 1 widget x: EvalText(value='1', description='x', layout=Layout(max_width='81em'))
Note that the @interact
decorator will alter the inputs to the function; instead of calling the function with a widget, it will be called with the value it produces.
Strings are evaluated into sage expressions with the sage_eval
function. This function treats a string as input to sage, and is demonstrated below:
sage_eval('pi^2')
pi^2
If you want to get access to the string without passing through sage_eval
, you can set type=str
. This can lead to some funny arithmetic:
@interact
def double_a_string(x = input_box(default=3, type=str)):
print(f'The double of x={x} is {2*x}')
Interactive function <function double_a_string at 0x7f429bbf9b20> with 1 widget x: TransformText(value='3', description='x', layout=Layout(max_width='81em'))
Observe that this type of input_box
as a string as its value. (Doubling a string appends the string to itself.)
i = input_box(default=3, type=str)
i.get_value()
'3'
Sliders¶
The numbers $0$ and $1$ below are minimum and maximum values respectively. The number 1/1000
is the step size. The initial value is 1/2
.
slider(0, 1, 1/1000, default=1/2, label='n value')
SelectionSlider(description='n value', index=500, options=(0, 1/1000, 1/500, 3/1000, 1/250, 1/200, 3/500, 7/1000, 1/125, 9/1000, 1/100, 11/1000, 3/250, 13/1000, 7/500, 3/200, 2/125, 17/1000, 9/500, 19/1000, 1/50, 21/1000, 11/500, 23/1000, 3/125, 1/40, 13/500, 27/1000, 7/250, 29/1000, 3/100, 31/1000, 4/125, 33/1000, 17/500, 7/200, 9/250, 37/1000, 19/500, 39/1000, 1/25, 41/1000, 21/500, 43/1000, 11/250, 9/200, 23/500, 47/1000, 6/125, 49/1000, 1/20, 51/1000, 13/250, 53/1000, 27/500, 11/200, 7/125, 57/1000, 29/500, 59/1000, 3/50, 61/1000, 31/500, 63/1000, 8/125, 13/200, 33/500, 67/1000, 17/250, 69/1000, 7/100, 71/1000, 9/125, 73/1000, 37/500, 3/40, 19/250, 77/1000, 39/500, 79/1000, 2/25, 81/1000, 41/500, 83/1000, 21/250, 17/200, 43/500, 87/1000, 11/125, 89/1000, 9/100, 91/1000, 23/250, 93/1000, 47/500, 19/200, 12/125, 97/1000, 49/500, 99/1000, 1/10, 101/1000, 51/500, 103/1000, 13/125, 21/200, 53/500, 107/1000, 27/250, 109/1000, 11/100, 111/1000, 14/125, 113/1000, 57/500, 23/200, 29/250, 117/1000, 59/500, 119/1000, 3/25, 121/1000, 61/500, 123/1000, 31/250, 1/8, 63/500, 127/1000, 16/125, 129/1000, 13/100, 131/1000, 33/250, 133/1000, 67/500, 27/200, 17/125, 137/1000, 69/500, 139/1000, 7/50, 141/1000, 71/500, 143/1000, 18/125, 29/200, 73/500, 147/1000, 37/250, 149/1000, 3/20, 151/1000, 19/125, 153/1000, 77/500, 31/200, 39/250, 157/1000, 79/500, 159/1000, 4/25, 161/1000, 81/500, 163/1000, 41/250, 33/200, 83/500, 167/1000, 21/125, 169/1000, 17/100, 171/1000, 43/250, 173/1000, 87/500, 7/40, 22/125, 177/1000, 89/500, 179/1000, 9/50, 181/1000, 91/500, 183/1000, 23/125, 37/200, 93/500, 187/1000, 47/250, 189/1000, 19/100, 191/1000, 24/125, 193/1000, 97/500, 39/200, 49/250, 197/1000, 99/500, 199/1000, 1/5, 201/1000, 101/500, 203/1000, 51/250, 41/200, 103/500, 207/1000, 26/125, 209/1000, 21/100, 211/1000, 53/250, 213/1000, 107/500, 43/200, 27/125, 217/1000, 109/500, 219/1000, 11/50, 221/1000, 111/500, 223/1000, 28/125, 9/40, 113/500, 227/1000, 57/250, 229/1000, 23/100, 231/1000, 29/125, 233/1000, 117/500, 47/200, 59/250, 237/1000, 119/500, 239/1000, 6/25, 241/1000, 121/500, 243/1000, 61/250, 49/200, 123/500, 247/1000, 31/125, 249/1000, 1/4, 251/1000, 63/250, 253/1000, 127/500, 51/200, 32/125, 257/1000, 129/500, 259/1000, 13/50, 261/1000, 131/500, 263/1000, 33/125, 53/200, 133/500, 267/1000, 67/250, 269/1000, 27/100, 271/1000, 34/125, 273/1000, 137/500, 11/40, 69/250, 277/1000, 139/500, 279/1000, 7/25, 281/1000, 141/500, 283/1000, 71/250, 57/200, 143/500, 287/1000, 36/125, 289/1000, 29/100, 291/1000, 73/250, 293/1000, 147/500, 59/200, 37/125, 297/1000, 149/500, 299/1000, 3/10, 301/1000, 151/500, 303/1000, 38/125, 61/200, 153/500, 307/1000, 77/250, 309/1000, 31/100, 311/1000, 39/125, 313/1000, 157/500, 63/200, 79/250, 317/1000, 159/500, 319/1000, 8/25, 321/1000, 161/500, 323/1000, 81/250, 13/40, 163/500, 327/1000, 41/125, 329/1000, 33/100, 331/1000, 83/250, 333/1000, 167/500, 67/200, 42/125, 337/1000, 169/500, 339/1000, 17/50, 341/1000, 171/500, 343/1000, 43/125, 69/200, 173/500, 347/1000, 87/250, 349/1000, 7/20, 351/1000, 44/125, 353/1000, 177/500, 71/200, 89/250, 357/1000, 179/500, 359/1000, 9/25, 361/1000, 181/500, 363/1000, 91/250, 73/200, 183/500, 367/1000, 46/125, 369/1000, 37/100, 371/1000, 93/250, 373/1000, 187/500, 3/8, 47/125, 377/1000, 189/500, 379/1000, 19/50, 381/1000, 191/500, 383/1000, 48/125, 77/200, 193/500, 387/1000, 97/250, 389/1000, 39/100, 391/1000, 49/125, 393/1000, 197/500, 79/200, 99/250, 397/1000, 199/500, 399/1000, 2/5, 401/1000, 201/500, 403/1000, 101/250, 81/200, 203/500, 407/1000, 51/125, 409/1000, 41/100, 411/1000, 103/250, 413/1000, 207/500, 83/200, 52/125, 417/1000, 209/500, 419/1000, 21/50, 421/1000, 211/500, 423/1000, 53/125, 17/40, 213/500, 427/1000, 107/250, 429/1000, 43/100, 431/1000, 54/125, 433/1000, 217/500, 87/200, 109/250, 437/1000, 219/500, 439/1000, 11/25, 441/1000, 221/500, 443/1000, 111/250, 89/200, 223/500, 447/1000, 56/125, 449/1000, 9/20, 451/1000, 113/250, 453/1000, 227/500, 91/200, 57/125, 457/1000, 229/500, 459/1000, 23/50, 461/1000, 231/500, 463/1000, 58/125, 93/200, 233/500, 467/1000, 117/250, 469/1000, 47/100, 471/1000, 59/125, 473/1000, 237/500, 19/40, 119/250, 477/1000, 239/500, 479/1000, 12/25, 481/1000, 241/500, 483/1000, 121/250, 97/200, 243/500, 487/1000, 61/125, 489/1000, 49/100, 491/1000, 123/250, 493/1000, 247/500, 99/200, 62/125, 497/1000, 249/500, 499/1000, 1/2, 501/1000, 251/500, 503/1000, 63/125, 101/200, 253/500, 507/1000, 127/250, 509/1000, 51/100, 511/1000, 64/125, 513/1000, 257/500, 103/200, 129/250, 517/1000, 259/500, 519/1000, 13/25, 521/1000, 261/500, 523/1000, 131/250, 21/40, 263/500, 527/1000, 66/125, 529/1000, 53/100, 531/1000, 133/250, 533/1000, 267/500, 107/200, 67/125, 537/1000, 269/500, 539/1000, 27/50, 541/1000, 271/500, 543/1000, 68/125, 109/200, 273/500, 547/1000, 137/250, 549/1000, 11/20, 551/1000, 69/125, 553/1000, 277/500, 111/200, 139/250, 557/1000, 279/500, 559/1000, 14/25, 561/1000, 281/500, 563/1000, 141/250, 113/200, 283/500, 567/1000, 71/125, 569/1000, 57/100, 571/1000, 143/250, 573/1000, 287/500, 23/40, 72/125, 577/1000, 289/500, 579/1000, 29/50, 581/1000, 291/500, 583/1000, 73/125, 117/200, 293/500, 587/1000, 147/250, 589/1000, 59/100, 591/1000, 74/125, 593/1000, 297/500, 119/200, 149/250, 597/1000, 299/500, 599/1000, 3/5, 601/1000, 301/500, 603/1000, 151/250, 121/200, 303/500, 607/1000, 76/125, 609/1000, 61/100, 611/1000, 153/250, 613/1000, 307/500, 123/200, 77/125, 617/1000, 309/500, 619/1000, 31/50, 621/1000, 311/500, 623/1000, 78/125, 5/8, 313/500, 627/1000, 157/250, 629/1000, 63/100, 631/1000, 79/125, 633/1000, 317/500, 127/200, 159/250, 637/1000, 319/500, 639/1000, 16/25, 641/1000, 321/500, 643/1000, 161/250, 129/200, 323/500, 647/1000, 81/125, 649/1000, 13/20, 651/1000, 163/250, 653/1000, 327/500, 131/200, 82/125, 657/1000, 329/500, 659/1000, 33/50, 661/1000, 331/500, 663/1000, 83/125, 133/200, 333/500, 667/1000, 167/250, 669/1000, 67/100, 671/1000, 84/125, 673/1000, 337/500, 27/40, 169/250, 677/1000, 339/500, 679/1000, 17/25, 681/1000, 341/500, 683/1000, 171/250, 137/200, 343/500, 687/1000, 86/125, 689/1000, 69/100, 691/1000, 173/250, 693/1000, 347/500, 139/200, 87/125, 697/1000, 349/500, 699/1000, 7/10, 701/1000, 351/500, 703/1000, 88/125, 141/200, 353/500, 707/1000, 177/250, 709/1000, 71/100, 711/1000, 89/125, 713/1000, 357/500, 143/200, 179/250, 717/1000, 359/500, 719/1000, 18/25, 721/1000, 361/500, 723/1000, 181/250, 29/40, 363/500, 727/1000, 91/125, 729/1000, 73/100, 731/1000, 183/250, 733/1000, 367/500, 147/200, 92/125, 737/1000, 369/500, 739/1000, 37/50, 741/1000, 371/500, 743/1000, 93/125, 149/200, 373/500, 747/1000, 187/250, 749/1000, 3/4, 751/1000, 94/125, 753/1000, 377/500, 151/200, 189/250, 757/1000, 379/500, 759/1000, 19/25, 761/1000, 381/500, 763/1000, 191/250, 153/200, 383/500, 767/1000, 96/125, 769/1000, 77/100, 771/1000, 193/250, 773/1000, 387/500, 31/40, 97/125, 777/1000, 389/500, 779/1000, 39/50, 781/1000, 391/500, 783/1000, 98/125, 157/200, 393/500, 787/1000, 197/250, 789/1000, 79/100, 791/1000, 99/125, 793/1000, 397/500, 159/200, 199/250, 797/1000, 399/500, 799/1000, 4/5, 801/1000, 401/500, 803/1000, 201/250, 161/200, 403/500, 807/1000, 101/125, 809/1000, 81/100, 811/1000, 203/250, 813/1000, 407/500, 163/200, 102/125, 817/1000, 409/500, 819/1000, 41/50, 821/1000, 411/500, 823/1000, 103/125, 33/40, 413/500, 827/1000, 207/250, 829/1000, 83/100, 831/1000, 104/125, 833/1000, 417/500, 167/200, 209/250, 837/1000, 419/500, 839/1000, 21/25, 841/1000, 421/500, 843/1000, 211/250, 169/200, 423/500, 847/1000, 106/125, 849/1000, 17/20, 851/1000, 213/250, 853/1000, 427/500, 171/200, 107/125, 857/1000, 429/500, 859/1000, 43/50, 861/1000, 431/500, 863/1000, 108/125, 173/200, 433/500, 867/1000, 217/250, 869/1000, 87/100, 871/1000, 109/125, 873/1000, 437/500, 7/8, 219/250, 877/1000, 439/500, 879/1000, 22/25, 881/1000, 441/500, 883/1000, 221/250, 177/200, 443/500, 887/1000, 111/125, 889/1000, 89/100, 891/1000, 223/250, 893/1000, 447/500, 179/200, 112/125, 897/1000, 449/500, 899/1000, 9/10, 901/1000, 451/500, 903/1000, 113/125, 181/200, 453/500, 907/1000, 227/250, 909/1000, 91/100, 911/1000, 114/125, 913/1000, 457/500, 183/200, 229/250, 917/1000, 459/500, 919/1000, 23/25, 921/1000, 461/500, 923/1000, 231/250, 37/40, 463/500, 927/1000, 116/125, 929/1000, 93/100, 931/1000, 233/250, 933/1000, 467/500, 187/200, 117/125, 937/1000, 469/500, 939/1000, 47/50, 941/1000, 471/500, 943/1000, 118/125, 189/200, 473/500, 947/1000, 237/250, 949/1000, 19/20, 951/1000, 119/125, 953/1000, 477/500, 191/200, 239/250, 957/1000, 479/500, 959/1000, 24/25, 961/1000, 481/500, 963/1000, 241/250, 193/200, 483/500, 967/1000, 121/125, 969/1000, 97/100, 971/1000, 243/250, 973/1000, 487/500, 39/40, 122/125, 977/1000, 489/500, 979/1000, 49/50, 981/1000, 491/500, 983/1000, 123/125, 197/200, 493/500, 987/1000, 247/250, 989/1000, 99/100, 991/1000, 124/125, 993/1000, 497/500, 199/200, 249/250, 997/1000, 499/500, 999/1000, 1), value=1/2)
Sliders can also choose from a list of values:
squares = [n^2 for n in range(10)]
slider(squares, default=9, label='square')
SelectionSlider(description='square', index=3, options=(0, 1, 4, 9, 16, 25, 36, 49, 64, 81), value=9)
@interact
def roots(x = slider(squares, default=49, label='square')):
print(f'x = {x}')
print(f'The roots of {x} are {sqrt(x)} and {-sqrt(x)}')
Interactive function <function roots at 0x7f429b8a5800> with 1 widget x: SelectionSlider(description='square', index=7, options=(0, 1, 4, 9, 16, 25, 36, 49, 64, 81), value=49)
Range slider¶
A range slider lets you select an interval, like $(30, 60) \subset \mathbb R$. For example:
range_slider(0,100,1,(50,90),label='Interval')
TransformIntRangeSlider(value=(50, 90), description='Interval')
@interact
def print_range(r = range_slider(0,100,1,(50,90),label='Interval')):
print(r)
print(type(r))
Interactive function <function print_range at 0x7f429bddaac0> with 1 widget r: TransformIntRangeSlider(value=(50, 90), description='Interval')
You can see above that a range slider produces a tuple.
Checkboxes:¶
checkbox(True, label='boolean')
Checkbox(value=True, description='boolean')
@interact
def boolean_printer(b = checkbox(True, label='boolean')):
print(b)
Interactive function <function boolean_printer at 0x7f42a40b3560> with 1 widget b: Checkbox(value=True, description='boolean')
Selector¶
By default, selector
displays a dropdown that lets you pick a value.
letters = [chr(ord('a') + i) for i in range(26)]
selector(letters, default='x', label='letter')
Dropdown(description='letter', index=23, options=('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'), value='x')
You can also display buttons instead.
letters = [chr(ord('a') + i) for i in range(26)]
selector(letters, default='x', label='letter', buttons=True)
ToggleButtons(description='letter', index=23, options=('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'), value='x')
nums = list(range(10))
print(nums)
@interact
def square(num = selector(nums, default=5, label='letter', buttons=True)):
print(f'The square of {num} is {num^2}')
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Interactive function <function square at 0x7f429b6f4220> with 1 widget num: ToggleButtons(description='letter', index=5, options=(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), value=5)
Input grid¶
Allows you to organize a rectangular box of numbers. For example:
input_grid(2, 3,
default=[ [1,2,3],
[4,5,6]], label='values')
Grid(value=[[1, 2, 3], [4, 5, 6]], children=(Label(value='values'), VBox(children=(EvalText(value='1', layout=Layout(max_width='5em')), EvalText(value='4', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='2', layout=Layout(max_width='5em')), EvalText(value='5', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='3', layout=Layout(max_width='5em')), EvalText(value='6', layout=Layout(max_width='5em'))))))
In a simple interact:
@interact
def box_values(box = input_grid(2, 3,
default=[ [1,2,3],
[4,5,6]], label='values')):
print(box)
Interactive function <function box_values at 0x7f42a408e160> with 1 widget box: Grid(value=[[1, 2, 3], [4, 5, 6]], children=(Label(value='values'), VBox(children=(EvalText(value='1', layout=Layout(max_width='5em')), EvalText(value='4', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='2', layout=Layout(max_width='5em')), EvalText(value='5', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='3', layout=Layout(max_width='5em')), EvalText(value='6', layout=Layout(max_width='5em'))))))
Note that the output is a list of lists of numbers. We can convert the list to a matrix if we wish.
@interact
def box_values(box = input_grid(3, 3,
default=[ [1,2,3],
[4,5,6],
[7,8,10]], label='values')):
m = matrix(box)
print(m)
print(f'The determinant of m is {m.det()}')
Interactive function <function box_values at 0x7f429ba0eca0> with 1 widget box: Grid(value=[[1, 2, 3], [4, 5, 6], [7, 8, 10]], children=(Label(value='values'), VBox(children=(EvalText(value='1', layout=Layout(max_width='5em')), EvalText(value='4', layout=Layout(max_width='5em')), EvalText(value='7', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='2', layout=Layout(max_width='5em')), EvalText(value='5', layout=Layout(max_width='5em')), EvalText(value='8', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='3', layout=Layout(max_width='5em')), EvalText(value='6', layout=Layout(max_width='5em')), EvalText(value='10', layout=Layout(max_width='5em'))))))
Color selector¶
The color_selector
lets the user pick a color. With all the widgets above, if you want to learn more about the widget, you can check the documentation with color_selector?
.
color_selector?
Signature: color_selector(default=(0, 0, 1), label=None, widget=None, hide_box=False) Docstring: A widget for choosing a color. INPUT: * "default" -- initial value * "label" -- optional label * "hide_box" -- (boolean) if True, do not show the textbox EXAMPLES: sage: from sage.repl.ipython_kernel.all_jupyter import color_selector sage: w = color_selector("orange", label="color me"); w SageColorPicker(value='#ffa500', description='color me') sage: w.get_interact_value() RGB color (1.0, 0.6470588235294118, 0.0) sage: color_selector(Color(0.1, 0.2, 0.3)) SageColorPicker(value='#19334c') Init docstring: Initialize self. See help(type(self)) for accurate signature. File: ~/Git/sage/sage/src/sage/repl/ipython_kernel/widgets_sagenb.py Type: function
Here is a simple selector, with a default orange:
color_selector("orange", label="color me")
SageColorPicker(value='#ffa500', description='color me')
We can change the color of this plot:
var('x')
@interact
def plot_sin(c = color_selector("orange", label="color me")):
return plot(sin(x), color=c)
Interactive function <function plot_sin at 0x7f429a542ac0> with 1 widget c: SageColorPicker(value='#ffa500', description='color me')
Examples¶
Problem 1¶
Create an interact that allows the user to select a function $f(x)$ and a point $x_0$. The interact should display the graph of $f$ over $[0, 1]$ in blue and display the tangent line to the graph at $\big(x_0, f(x_0)\big)$ in red.
In order to address this problem, we try to accomplish the task with some example first. Here we choose an f
and an x_0
:
f = sin(10*x)
x_0 = 3/4
Note that we use f = sin(10*x)
because this is what we would use as an argument in our interact. We find it easier to work with a function than an expression, so we can fix this with:
f(x) = f
To compute the slope of the tangent line, we need the derivative:
df = f.derivative(x)
df
x |--> 10*cos(10*x)
Now we can compute the derivative at x_0
. This will be the slope of our tangent line.
m = df(x_0)
m
10*cos(15/2)
We use point-slope form to write an equation for the tangent line:
l(x) = m*(x-x_0) + f(x_0)
l
x |--> 5/2*(4*x - 3)*cos(15/2) + sin(15/2)
We will plot several objects. First the graph of f
:
plt1 = plot(f, xmin=0, xmax=1, color='blue')
plt1
Then we add the tangent line:
plt2 = plot(l, xmin=0, xmax=1, color='red')
plt1 + plt2
Last, we might include the point:
plt3 = point([(x_0, f(x_0))])
plt1 + plt2 + plt3
We can then put this into our interact:
@interact
def tangent_line(f = sin(10*x), x_0=3/4):
f(x)=f
df = f.derivative(x)
m = df(x_0)
l(x) = m*(x-x_0) + f(x_0)
plt1 = plot(f, xmin=0, xmax=1, color='blue')
plt2 = plot(l, xmin=0, xmax=1, color='red')
plt3 = point([(x_0, f(x_0))])
return plt1+plt2+plt3
Interactive function <function tangent_line at 0x7f429a486700> with 2 widgets f: EvalText(value='sin(10*x)', description='f') x_0: FloatSlider(value=0.75, description='x_0', max=2.25, min=-0.75)
Now we make some improvements:
- As we vary the parameter, the graph moves in a jarring way. We'd like the bounding box to be controled by the graph of
f
and for us to see whatever portion of the line is contained in this box. We can fix this by using bounding box data from the plot off
to create a bounding box for our graph of the tangent line. Bounding box data for a plt likeplt1
is accessible viaplt1.xmin()
,plt1.xmax()
,plt1.ymin()
andplt1.ymax()
. - The default slider for
x_0 = 3/4
seems to vary between-0.75
and2.25
. But it doesn't make much sense to choose a point outside of the interval $[0, 1]$. We can create aslider
that will do a better job. Another possibility might be to use aninput_box
.
These issues are fixed in the version below:
@interact
def tangent_line(
f = sin(10*x),
x_0=slider(0, 1, 0.001, default=1/2, label=r'$x_0$')
):
f(x)=f
df = f.derivative(x)
m = df(x_0)
l(x) = m*(x-x_0) + f(x_0)
plt1 = plot(f, xmin=0, xmax=1, color='blue')
plt2 = plot(l, xmin=0, xmax=1, ymin=plt1.ymin(), ymax=plt1.ymax(), color='red')
plt3 = point([(x_0, f(x_0))],zorder=10, size=20, color='black')
return plt1+plt2+plt3
Interactive function <function tangent_line at 0x7f429a58df80> with 2 widgets f: EvalText(value='sin(10*x)', description='f') x_0: TransformFloatSlider(value=0.5, description='$x_0$', max=1.0, step=0.001)
Problem 2¶
Five points in the plane determine a quadric curve passing through them, the zero set of a degree two polynomial equation of the form $$a x^2 + b xy+ c x + dy^2 + e y + f = 0,$$ with $a$, $b$, $c$, $d$, $e$, and $f$ constants.
Find this curve and plot it together with the $5$ points. Make it interactive.
We will use an input_grid
to get the points. We'll use columns for our points (otherwise it would take up $5$ rows).
points = input_grid(2, 5,
default=[ [1,8,3,9,5],
[4,8,6,2,2]], label='points')
points
Grid(value=[[1, 8, 3, 9, 5], [4, 8, 6, 2, 2]], children=(Label(value='points'), VBox(children=(EvalText(value='1', layout=Layout(max_width='5em')), EvalText(value='4', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='8', layout=Layout(max_width='5em')), EvalText(value='8', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='3', layout=Layout(max_width='5em')), EvalText(value='6', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='9', layout=Layout(max_width='5em')), EvalText(value='2', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='5', layout=Layout(max_width='5em')), EvalText(value='2', layout=Layout(max_width='5em'))))))
When the data is passed to our function through @interact
we will be given the following type of value:
points = points.get_value()
points
[[1, 8, 3, 9, 5], [4, 8, 6, 2, 2]]
This is a list of five $x$-coordinates followed by a list of five $y$-coordinates. Let us organize the points into five points.
pts = []
for i in range(5):
pt = (points[0][i], points[1][i])
pts.append(pt)
pts
[(1, 4), (8, 8), (3, 6), (9, 2), (5, 2)]
We plot the points below. (We set zorder=10
so it appears on top.)
plt1 = point(pts, color='red',zorder=10, size=40)
plt1
Now let us define a general version of the quadratic polynomial.
a,b,c,d,e,f = var('a b c d e f')
Q(x,y) = a*x^2 + b*x*y + c*x + d*y^2 + e*y + f
Q
(x, y) |--> a*x^2 + b*x*y + d*y^2 + c*x + e*y + f
We get an equation that must hold from each point:
eqns = [Q(*pts[i])==0 for i in range(5)]
eqns
[a + 4*b + c + 16*d + 4*e + f == 0, 64*a + 64*b + 8*c + 64*d + 8*e + f == 0, 9*a + 18*b + 3*c + 36*d + 6*e + f == 0, 81*a + 18*b + 9*c + 4*d + 2*e + f == 0, 25*a + 10*b + 5*c + 4*d + 2*e + f == 0]
Observe that this is a homogeneous system of $5$ linear equations in $6$ variables. Therefore, there is at least a $1=6-5$-dimensional solution set, which is a linear subspace of $\mathbb R^6$.
We can solve the system:
solutions = solve(eqns,[a,b,c,d,e,f], solution_dict=True)
solutions
[{a: 1/148*r1, b: -19/888*r1, c: -23/444*r1, d: 29/444*r1, e: -425/888*r1, f: r1}]
We see there is only one solution family, which has a single free variable. We extract the single solution:
sol = solutions[0]
We can extract the variables in a symbolic expression with the .variables()
method. For example:
sol[a].variables()
(r1,)
The following will extract all the variables and place them in a set:
free_variables = set()
for variable, expression in sol.items():
free_variables = free_variables.union(expression.variables())
free_variables
{r1}
We want one solution, so we will choose values for these free variables. We don't want to choose zero, or else all our values will be zero. The choice of one for all values should be okay. Here is a dictionary that represents this choice:
free_variable_values = {variable:1 for variable in free_variables}
free_variable_values
{r1: 1}
Now let's build a new solution dictionary that substutes these free variables.
new_sol = {}
for variable, expression in sol.items():
new_expression = expression.subs(free_variable_values)
new_sol[variable] = new_expression
new_sol
{a: 1/148, b: -19/888, c: -23/444, d: 29/444, e: -425/888, f: 1}
Great. Now we have an actual function to graph the zero set of:
Q_new = Q.subs(new_sol)
Q_new
(x, y) |--> 1/148*x^2 - 19/888*x*y + 29/444*y^2 - 23/444*x - 425/888*y + 1
To plot it, we can try a contour plot:
contour_plot(Q_new, (x, 0, 10), (y, 0, 10)) + plt1
We are hoping to draw just the zero set. We can use contour_plot
but it requires some customization. We read the documentation (contour_plot?
) and after some experimentation settle on the following:
contour_plot(Q_new, (x, 0, 10), (y, 0, 10), fill=False, contours=[0]) + plt1
We now assemble the above into an interactive plot:
@interact
def plot_curve(points = input_grid(2, 5,
default=[ [1,8,3,9,5],
[4,8,6,2,2]], label='points')):
pts = []
for i in range(5):
pt = (points[0][i], points[1][i])
pts.append(pt)
pts
# We plot the points below. (We set `zorder=10` so it appears on top.)
plt1 = point(pts, color='red',zorder=10, size=40)
# Now let us define a general version of the quadratic polynomial.
a,b,c,d,e,f = var('a b c d e f')
Q(x,y) = a*x^2 + b*x*y + c*x + d*y^2 + e*y + f
# We get an equation that must hold from each point:
eqns = [Q(*pts[i])==0 for i in range(5)]
# We can solve the system:
solutions = solve(eqns,[a,b,c,d,e,f], solution_dict=True)
sol = solutions[0]
# The following will extract all the variables and place them in a set:
free_variables = set()
for variable, expression in sol.items():
free_variables = free_variables.union(expression.variables())
free_variables
# We want one solution, so we will choose values for these free variables.
free_variable_values = {variable:1 for variable in free_variables}
# Now let's build a new solution dictionary that substutes these free variables.
new_sol = {}
for variable, expression in sol.items():
new_expression = expression.subs(free_variable_values)
new_sol[variable] = new_expression
new_sol
# Great. Now we have an actual function to graph the zero set of:
Q_new = Q.subs(new_sol)
# We plot the zero contour below:
plt2 = contour_plot(Q_new, (x, 0, 10), (y, 0, 10), fill=False, contours=[0])
# Return the combined plot
return plt1 + plt2
Interactive function <function plot_curve at 0x7f429a05bd80> with 1 widget points: Grid(value=[[1, 8, 3, 9, 5], [4, 8, 6, 2, 2]], children=(Label(value='points'), VBox(children=(EvalText(value='1', layout=Layout(max_width='5em')), EvalText(value='4', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='8', layout=Layout(max_width='5em')), EvalText(value='8', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='3', layout=Layout(max_width='5em')), EvalText(value='6', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='9', layout=Layout(max_width='5em')), EvalText(value='2', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='5', layout=Layout(max_width='5em')), EvalText(value='2', layout=Layout(max_width='5em'))))))